﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using Oxage.Common;
using Oxage.Forms;

namespace Oxage.Midi
{
	public class SerialPortConnector : IConnector
	{
		#region Members
		private SerialPort port;
		private SerialPortForm panel;
		#endregion

		#region Constructor
		public SerialPortConnector()
		{
			panel = new SerialPortForm();

			port = new SerialPort();
			port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
		}
		#endregion

		#region Properties
		public int BaudRate
		{
			get
			{
				return port.BaudRate;
			}
			set
			{
				port.BaudRate = value;
			}
		}

		public int DataBits
		{
			get
			{
				return port.DataBits;
			}
			set
			{
				port.DataBits = value;
			}
		}

		public StopBits StopBits
		{
			get
			{
				return port.StopBits;
			}
			set
			{
				port.StopBits = value;
			}
		}

		public Parity Parity
		{
			get
			{
				return port.Parity;
			}
			set
			{
				port.Parity = value;
			}
		}

		public string PortName
		{
			get
			{
				return port.PortName;
			}
			set
			{
				port.PortName = value;
			}
		}
		#endregion

		#region Methods
		public override string ToString()
		{
			return "Serial port";
		}
		#endregion

		#region Event handlers
		private Queue<byte> midiQueue = new Queue<byte>(); // unprocessed data
		private Queue<byte> midiMsg = new Queue<byte>(); // msg to be sent
		private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			//TODO: Improve and clean
			int bytes = port.BytesToRead;
			byte[] buffer = new byte[bytes];
			port.Read(buffer, 0, bytes);
			for (int c = 0; c < bytes; c++)
			{
				midiQueue.Enqueue(buffer[c]);
			}

			while (midiQueue.Count > 0)
			{
				midiMsg.Enqueue(midiQueue.Dequeue());
				if (midiMsg.Count == 3)
				{
					byte byteOne = midiMsg.Dequeue();
					byte byteTwo = midiMsg.Dequeue();
					byte byteThree = midiMsg.Dequeue();
					// sanatize everything for MidiPlayer
					if (byteTwo > 127) byteTwo = 127;
					//if (byteThree > 127) byteThree = 127;
					byteThree = 127;

					MidiMessage message = null;

					try
					{
						if (byteOne >= 0x90 && byteOne <= 0x9F)
						{
							byteOne -= 0x90; //Octave shift?!
							message = new MidiMessage() { Event = MidiEvents.NoteOn, Channel = byteOne, Note = byteTwo, Velocity = byteThree };
						}
						else if (byteOne >= 0x80 && byteOne <= 0x8F)
						{
							byteOne -= 0x80;
							message = new MidiMessage() { Event = MidiEvents.NoteOff, Channel = byteOne, Note = byteTwo, Velocity = byteThree };

						}
						//else if (byteOne >= 0xB0 && byteOne <= 0xBF && !this.isThru)
						//{
						//  byteOne -= 0xB0;
						//  this.LastInputChannel = byteOne;
						//  MidiPlayer.Play(new Controller(0, byteOne, byteTwo, byteThree));
						//}
					}
					catch
					{
					}

					OnReceived(message);
				}
			}
		}
		#endregion

		#region IConnector Members
		public bool IsOpened
		{
			get { return (port != null && port.IsOpen); }
		}

		public DataDirection Direction
		{
			get { return DataDirection.Both; }
		}

		public void Open()
		{
			if (port != null && port.IsOpen)
			{
				port.Close();
				panel.IsLocked = false;
			}
			port.PortName = panel.PortName;
			port.BaudRate = panel.BaudRate;
			port.DataBits = panel.DataBits;
			port.StopBits = panel.StopBits;
			port.Parity = panel.Parity;
			port.Handshake = panel.Handshake;
			port.Open();
			panel.IsLocked = true;
		}

		public void Close()
		{
			if (port != null && port.IsOpen)
			{
				port.Close();
			}
			panel.IsLocked = false;
		}

		public void Send(IMessage message)
		{
			byte[] buffer = message.Encode();
			port.Write(buffer, 0, buffer.Length);
		}

		public event EventHandler<EventArgs<IMessage>> Received;
		protected virtual void OnReceived(IMessage message)
		{
			if (Received != null)
			{
				Received(this, new EventArgs<IMessage>(message));
			}
		}

		public IConfigPanel GetConfigPanel()
		{
			return panel;
		}
		#endregion
	}
}
